Skip to content

Skip MCP Apps UI form when issue_write update includes a state change#2163

Merged
SamMorrowDrums merged 3 commits intomainfrom
copilot/fix-mcp-ui-issue
Mar 6, 2026
Merged

Skip MCP Apps UI form when issue_write update includes a state change#2163
SamMorrowDrums merged 3 commits intomainfrom
copilot/fix-mcp-ui-issue

Conversation

Copy link
Contributor

Copilot AI commented Mar 6, 2026

Summary

Skip the MCP Apps UI edit form when issue_write is called with method: "update" and a state parameter, since the form only handles title/body and would silently drop the state transition.

Why

When closing or reopening an issue via issue_write, the UI gate intercepted the call and rendered a title/body edit form — which has no state controls. The user saw an irrelevant "Update issue" form instead of the issue actually being closed.

What changed

  • pkg/github/issues.go: In the insiders UI gate, check for state key presence in args on update — if present, fall through to direct execution instead of returning the form message. Uses direct map key lookup (args["state"]) rather than OptionalParam to avoid silently ignoring type errors.
  • pkg/github/issues_test.go: Added two test cases: state change bypasses UI form; title-only update still shows form

MCP impact

  • Tool schema or behavior changed
    • issue_write with method: "update" + state now executes directly on UI-capable clients instead of showing the edit form.

Prompts tested (tool changes only)

Security / limits

  • No security or limits impact

Tool renaming

  • I am not renaming tools as part of this PR

Lint & tests

  • Linted locally with ./script/lint
  • Tested locally with ./script/test

Docs

  • Not needed

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

When using issue_write with method "update" and a state parameter (e.g.
"closed"), the MCP Apps UI form was incorrectly shown. The form only
handles title/body editing and would lose the state transition. Now when
a state change is requested, the UI form is skipped and the update
executes directly.

Fixes #798

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incorrect MCP UI displayed on issue updates Skip MCP Apps UI form when issue_write update includes a state change Mar 6, 2026
@mattdholloway mattdholloway marked this pull request as ready for review March 6, 2026 12:08
@mattdholloway mattdholloway requested a review from a team as a code owner March 6, 2026 12:08
Copilot AI review requested due to automatic review settings March 6, 2026 12:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the issue_write MCP tool’s “Insiders mode” UI gating so UI-capable clients don’t get redirected to the MCP Apps edit form when an update request includes a state transition (since the current UI form only submits title/body and would drop the state change).

Changes:

  • Update the issue_write insiders UI gate to bypass the form when method: "update" includes a state parameter.
  • Add unit tests covering: (1) state-change updates bypass the UI form, (2) title-only updates still return the UI form message.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/github/issues.go Adjusts insiders UI-gate logic to skip the form for updates that include a state change.
pkg/github/issues_test.go Adds test coverage for the new UI-gate bypass behavior for state changes vs title-only updates.
Comments suppressed due to low confidence (1)

pkg/github/issues.go:1096

  • The comment notes the UI form only edits title/body, but the UI gate still returns the form for method == "update" whenever state is not set. That means UI clients attempting to update other supported fields (e.g., labels, assignees, milestone, type, state_reason/duplicate_of) will still be routed through a form submission that drops those fields. Consider expanding the bypass condition to skip the form whenever any non-title/body update parameters are present, or update the UI app to preserve/submit them, to avoid silent partial updates.
					// Skip the UI form when a state change is requested because
					// the form only handles title/body editing and would lose the
					// state transition (e.g. closing or reopening the issue).
					state, _ := OptionalParam[string](args, "state")
					if state == "" {
						issueNumber, numErr := RequiredInt(args, "issue_number")
						if numErr != nil {
							return utils.NewToolResultError("issue_number is required for update method"), nil, nil
						}
						return utils.NewToolResultText(fmt.Sprintf("Ready to update issue #%d in %s/%s. IMPORTANT: The issue has NOT been updated yet. Do NOT tell the user the issue was updated. The user MUST click Submit in the form to update it.", issueNumber, owner, repo)), nil, nil
					}
				} else {
					return utils.NewToolResultText(fmt.Sprintf("Ready to create an issue in %s/%s. IMPORTANT: The issue has NOT been created yet. Do NOT tell the user the issue was created. The user MUST click Submit in the form to create it.", owner, repo)), nil, nil
				}

Check for the "state" key directly in the args map rather than using
OptionalParam and ignoring its error. This ensures that a wrongly-typed
state value bypasses the UI form (falling through to the normal
validation path) instead of silently showing the form.

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 6, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • raw.ghe.example.com
    • Triggering command: /tmp/go-build3890635722/b360/oauth.test /tmp/go-build3890635722/b360/oauth.test -test.testlogfile=/tmp/go-build3890635722/b360/testlog.txt -test.paniconexit0 -test.timeout=10m0s eRG8IhS8FLY_lk77MXqq/eRG8IhS8FLY_lk77MXqq -goversion go1.24.13 -c=4 -race -nolocalimports -importcfg /tmp/go-build3890635722/b246/importcfg -uns�� g_.a /tmp/go-build2252200762/b168/vet--64 k0VW602G9 (dns block)
  • raw.mycompanygithub.com
    • Triggering command: /tmp/go-build3890635722/b392/utils.test /tmp/go-build3890635722/b392/utils.test -test.testlogfile=/tmp/go-build3890635722/b392/testlog.txt -test.paniconexit0 -test.timeout=10m0s fj6nrGtGrUSOUlb2ZNLQ/fj6nrGtGrUSOUlb2ZNLQ -goversion go1.24.13 -c=4 -race -nolocalimports -importcfg /tmp/go-build3890635722/b318/importcfg 8505�� g_.a -buildtags (dns block)
  • raw.myghe.com
    • Triggering command: /tmp/go-build3890635722/b392/utils.test /tmp/go-build3890635722/b392/utils.test -test.testlogfile=/tmp/go-build3890635722/b392/testlog.txt -test.paniconexit0 -test.timeout=10m0s fj6nrGtGrUSOUlb2ZNLQ/fj6nrGtGrUSOUlb2ZNLQ -goversion go1.24.13 -c=4 -race -nolocalimports -importcfg /tmp/go-build3890635722/b318/importcfg 8505�� g_.a -buildtags (dns block)
  • raw.notgithub.com
    • Triggering command: /tmp/go-build3890635722/b392/utils.test /tmp/go-build3890635722/b392/utils.test -test.testlogfile=/tmp/go-build3890635722/b392/testlog.txt -test.paniconexit0 -test.timeout=10m0s fj6nrGtGrUSOUlb2ZNLQ/fj6nrGtGrUSOUlb2ZNLQ -goversion go1.24.13 -c=4 -race -nolocalimports -importcfg /tmp/go-build3890635722/b318/importcfg 8505�� g_.a -buildtags (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copy link
Collaborator

@SamMorrowDrums SamMorrowDrums left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing quickly @mattdholloway! ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants